package com.lu.core.support;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.lu.core.annotations.SystemLog;
import com.lu.mapper.SystemBusinessLogDetailMapper;
import com.lu.mapper.SystemBusinessLogMapper;
import com.lu.model.domain.SystemMethodLog;
import com.lu.model.entity.SystemBusinessLog;
import com.lu.model.entity.SystemBusinessLogDetail;
import com.lu.model.enums.YesOrNoEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * @Description 记录系统日志工具
 * @Author: zhanglu
 * @Date: 2022/7/22 11:23
 */
@Slf4j
public class LogHelper {

  public final static InheritableThreadLocal<List<SystemMethodLog>> methodLogsThreadLocal = new InheritableThreadLocal<>();
  private final static ThreadPoolTaskExecutor systemLogHelperExecutor = SpringUtil.getBean("systemLogHelperExecutor", ThreadPoolTaskExecutor.class);
  private final static SystemBusinessLogMapper systemBusinessLogMapper = SpringUtil.getBean("systemBusinessLogMapper", SystemBusinessLogMapper.class);
  private final static SystemBusinessLogDetailMapper systemBusinessLogDetailMapper = SpringUtil.getBean("systemBusinessLogDetailMapper", SystemBusinessLogDetailMapper.class);

  /**
   * 记录入库
   */
  public static void handler(){
    List<SystemMethodLog> systemMethodLogs = methodLogsThreadLocal.get();
    if(ObjectUtil.isEmpty(systemMethodLogs)){
      return;
    }
    systemLogHelperExecutor.execute(() -> {
      try {
        SystemMethodLog first = null;
        for (SystemMethodLog log : systemMethodLogs){
          if(ObjectUtil.isNotEmpty(log.getIp())){
            //父 入库
            SystemBusinessLog businessLog = new SystemBusinessLog();
            businessLog.setIp(log.getIp());
            businessLog.setScene(log.getScene());
            businessLog.setCode(log.getCode());
            businessLog.setMethod(log.getMethod());
            businessLog.setStatus(ObjectUtil.isEmpty(log.getStack()) ? YesOrNoEnum.YES.getValue() : YesOrNoEnum.NO.getValue());
            businessLog.setDescription(log.getDescription());
            systemBusinessLogMapper.insert(businessLog);
            log.setId(businessLog.getId());
            SystemBusinessLogDetail businessLogDetail = new SystemBusinessLogDetail();
            businessLogDetail.setSystemBusinessLogId(businessLog.getId());
            businessLogDetail.setInParams(log.getInParams());
            businessLogDetail.setOutParams(log.getOutParams());
            businessLogDetail.setStack(log.getStack());
            systemBusinessLogDetailMapper.insert(businessLogDetail);
            first = log;
            break;
          }
        }
        for (SystemMethodLog log : systemMethodLogs) {
          if(log.getId() != null){
            continue;
          }
          if(first != null){
            log.setPid(first.getId());
            SystemBusinessLog businessLog = new SystemBusinessLog();
            businessLog.setIp(log.getIp());
            businessLog.setPid(first.getId());
            businessLog.setScene(log.getScene());
            businessLog.setCode(log.getCode());
            businessLog.setMethod(log.getMethod());
            businessLog.setStatus(ObjectUtil.isEmpty(log.getStack()) ? YesOrNoEnum.YES.getValue() : YesOrNoEnum.NO.getValue());
            businessLog.setDescription(log.getDescription());
            systemBusinessLogMapper.insert(businessLog);
            SystemBusinessLogDetail businessLogDetail = new SystemBusinessLogDetail();
            businessLogDetail.setSystemBusinessLogId(businessLog.getId());
            businessLogDetail.setInParams(log.getInParams());
            businessLogDetail.setOutParams(log.getOutParams());
            businessLogDetail.setStack(log.getStack());
            systemBusinessLogDetailMapper.insert(businessLogDetail);
          }
        }
      } catch (Exception e) {
        log.error("系统业务日志入库异常：" + e);
      }finally{
        LogHelper.remove();
      }
    });
  }

  public static void remove(){
    try {
      LogHelper.methodLogsThreadLocal.remove();
    } catch (Exception e) {
      log.error("清空线程本地数据异常：" + e);
    }
  }

  /**
   * 系统日志实体构建
   * @param ip
   * @param scene
   * @param code
   * @param packageClass
   * @param inParams
   * @param outParams
   * @param description
   * @param e
   */
  public static void build(String ip, SystemMethodLog.SCENE scene, SystemMethodLog.Code code,
                           String packageClass, Object inParams, Object outParams,
                           String description, Exception e){
    if(ObjectUtil.isEmpty(LogHelper.methodLogsThreadLocal.get())){
      LogHelper.methodLogsThreadLocal.set(new ArrayList<>());
    }
    SystemMethodLog systemMethodLog = new SystemMethodLog();
    systemMethodLog.setIp(ip);
    systemMethodLog.setScene(scene.name());
    systemMethodLog.setCode(code.name());
    systemMethodLog.setMethod(packageClass);
    systemMethodLog.setInParams(objToJSonOrStr(inParams));
    systemMethodLog.setOutParams(objToJSonOrStr(outParams));
    systemMethodLog.setDescription("["+Thread.currentThread().getName()+"]：" + (ObjectUtil.isEmpty(description) ? "" : description));
    systemMethodLog.setStack(e != null ? exceptionStackTrace(e) : null);
    methodLogsThreadLocal.get().add(systemMethodLog);
  }

  public static void build(String ip, SystemLog systemLog,
                           String packageClass, Object inParams, Object outParams,
                           String description, Exception e){
    build(ip, systemLog.code().getScene(), systemLog.code(), packageClass, inParams, outParams, description, e);
  }

  public static void build(String ip, SystemLog systemLog,
                           String packageClass, Object inParams, Object outParams){
    build(ip, systemLog, packageClass, inParams, outParams, null, null);
  }

  public static void build(String ip, SystemLog systemLog,
                           String packageClass, Object inParams, Object outParams, Exception e){
    build(ip, systemLog, packageClass, inParams, outParams, null, e);
  }


  public static void build(SystemMethodLog.Code code,
                           String packageClass, Object inParams, Object outParams,
                           String description){
    build(null, code.getScene(), code, packageClass, inParams, outParams, description, null);
  }

  public static void build(SystemMethodLog.Code code,
                           String packageClass, Object inParams, Object outParams,
                           String description, Exception e){
    build(null, code.getScene(), code, packageClass, inParams, outParams, description, e);
  }

  public static void build(SystemMethodLog.Code code,
                           String packageClass, Object inParams, Object outParams){
    build(null, code.getScene(), code, packageClass, inParams, outParams, null, null);
  }

  /**
   * 参数解析组装
   * @param os
   * @return
   */
  public static String params(Object... os){
    try {
      if(os == null){
        return null;
      }
      Integer index = 1;
      Map<String, Object> params = new LinkedHashMap<>();
      for (Object o : os) {
        params.put(index.toString(), o);
        index ++;
      }
      return JSON.toJSONString(params);
    } catch (Exception e) {
      log.error("系统业务日志参数解析异常：" + e);
    }
    return os.toString();
  }

  /**
   * 对象转换成 json 或者 字符串
   * @param obj
   * @return
   */
  public static String objToJSonOrStr(Object obj){
    try {
      if(obj == null){
        return null;
      }
      String json = JSON.toJSONString(obj);
      return JSONUtil.isJson(json) ? json : obj.toString();
    }catch (Exception e){
      log.error("系统业务日志obj转换异常：" + e);
    }
    return obj.toString();
  }

  /**
   * 获取包名类名方法名
   * @param stackTraces
   * @param targetName
   * @return
   */
  public static String packageClass(StackTraceElement[] stackTraces, String targetName){
    for (int i = 0; i < stackTraces.length; i++) {
      StackTraceElement stackTrace = stackTraces[i];
      if(stackTrace.getClassName().equals(targetName)){
        return stackTrace.getClassName() + "." + stackTrace.getMethodName();
      }
    }
    return null;
  }

  /**
   * 获取堆栈信息
   * @param stackTraces
   * @return
   */
  public static String stackTrace(StackTraceElement[] stackTraces){
    StringBuilder stack = new StringBuilder();
    for (int i = 0; i < stackTraces.length; i++) {
      stack.append(stackTraces[i].toString() + "\n");
    }
    return ObjectUtil.isEmpty(stack) ? null : stack.toString();
  }

  /**
   * 异常堆栈信息  (输出信息太多了，有待优化)
   * @param e
   * @return
   */
  public static String exceptionStackTrace(Exception e) {
    StringWriter sw = null;
    PrintWriter pw = null;
    try {
      sw = new StringWriter();
      pw = new PrintWriter(sw);
      // 将出错的栈信息输出到printWriter中
      e.printStackTrace(pw);
      pw.flush();
      sw.flush();
    } finally {
      if (sw != null) {
        try {
          sw.close();
        } catch (IOException e1) {
          e1.printStackTrace();
        }
      }
      if (pw != null) {
        pw.close();
      }
    }
    return sw.toString();
  }

}
